package com.walking.aspect;

import com.walking.db.CurDataSource;
import com.walking.db.DataSourceHolder;
import com.walking.db.DataSourceName;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Objects;

/**
 * 动态数据源切面类
 * 被切中的，则先判断方法上是否有CurDataSource注解
 * 然后判断方法所属类上是否有CurDataSource注解
 * 其次判断是否配置了包级别的数据源
 *
 * 优先级为方法、类、包
 * 若同时配置则优先按方法上的
 *
 * @author walking
 * 公众号：编程大道
 */
@Slf4j
@Aspect
@Component
public class DynamicDataSourceAspect {
    // pointCut
    @Pointcut("@annotation(com.walking.db.CurDataSource)")
    public void choseDatasourceByAnnotation() {
    }

    @Pointcut("@within(com.walking.db.CurDataSource)")
    public void choseDatasourceByClass() {
    }

    @Pointcut("execution(* com.walking.service3..*(..))")
    public void choseDatasourceByPackage() {
    }

    @Around("choseDatasourceByAnnotation() || choseDatasourceByClass() || choseDatasourceByPackage()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("===进入AOP环绕通知===");
        Signature signature = joinPoint.getSignature();
        DataSourceName datasourceName = getDatasourceKey(signature);
        if (!Objects.isNull(datasourceName)) {
            DataSourceHolder.setDataSourceName(datasourceName);
        }
        return joinPoint.proceed();
    }

    private DataSourceName getDatasourceKey(Signature signature) {
        if (signature == null) {
            return null;
        } else {
            if (signature instanceof MethodSignature) {
                MethodSignature methodSignature = (MethodSignature) signature;
                Method method = methodSignature.getMethod();
                if (method.isAnnotationPresent(CurDataSource.class)) {
                    return this.dsSettingInMethod(method);
                }
                Class<?> declaringClass = method.getDeclaringClass();
                if (declaringClass.isAnnotationPresent(CurDataSource.class)) {
                    return this.dsSettingInConstructor(declaringClass);
                }
                Package aPackage = declaringClass.getPackage();
                this.dsSettingInPackage(aPackage);
            }
            return null;
        }
    }

    private DataSourceName dsSettingInConstructor(Class<?> declaringClass) {
        CurDataSource dataSource = declaringClass.getAnnotation(CurDataSource.class);
        return dataSource.value();
    }

    private DataSourceName dsSettingInMethod(Method method) {
        CurDataSource dataSource = method.getAnnotation(CurDataSource.class);
        return dataSource.value();
    }

    private void dsSettingInPackage(Package pkg) {
        DataSourceHolder.usePackageDatasourceKey(pkg.getName());
    }
}
